/*******************************************************************************
 * Copyright (c) 2013 Liviu Ionescu.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 * 
 * Contributors:
 *     Liviu Ionescu - initial version
 *******************************************************************************/

package com.espressif.idf.debug.gdbjtag.openocd.dsf;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.RejectedExecutionException;

import org.eclipse.cdt.debug.gdbjtag.core.IGDBJtagConstants;
import org.eclipse.cdt.dsf.concurrent.DefaultDsfExecutor;
import org.eclipse.cdt.dsf.concurrent.DsfRunnable;
import org.eclipse.cdt.dsf.concurrent.IDsfStatusConstants;
import org.eclipse.cdt.dsf.gdb.IGDBLaunchConfigurationConstants;
import org.eclipse.cdt.dsf.gdb.internal.GdbPlugin;
import org.eclipse.cdt.dsf.service.DsfServicesTracker;
import org.eclipse.cdt.dsf.service.DsfSession;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
import org.eclipse.debug.core.model.IProcess;
import org.eclipse.debug.core.model.ISourceLocator;
import org.eclipse.embedcdt.debug.gdbjtag.core.dsf.GnuMcuLaunch;

import com.espressif.idf.debug.gdbjtag.openocd.Activator;
import com.espressif.idf.debug.gdbjtag.openocd.Configuration;
import com.espressif.idf.debug.gdbjtag.openocd.ConfigurationAttributes;
import com.espressif.idf.debug.gdbjtag.openocd.dsf.process.CustomIdfProcessFactory;
import com.espressif.idf.debug.gdbjtag.openocd.preferences.DefaultPreferences;

@SuppressWarnings("restriction")
public class Launch extends GnuMcuLaunch
{

	// ------------------------------------------------------------------------

	ILaunchConfiguration fConfig = null;
	private DsfSession fSession;
	private DsfServicesTracker fTracker;
	private DefaultDsfExecutor fExecutor;
	private IProcess openOcdServerProcess;
	private IProcess gdbIProcess;
	
	private static final String SERVER_PROC_KEY = "SERVER_PROC";
	private static final String GDB_PROC_KEY = "GDB_PROC";

	// ------------------------------------------------------------------------

	public Launch(ILaunchConfiguration launchConfiguration, String mode, ISourceLocator locator)
	{

		super(launchConfiguration, mode, locator);

		if (Activator.getInstance().isDebugging())
		{
			System.out.println("openocd.Launch.launch(" + launchConfiguration.getName() + "," + mode + ") " + this);
		}

		fConfig = launchConfiguration;
		fExecutor = (DefaultDsfExecutor) getDsfExecutor();
		fSession = getSession();
	}

	// ------------------------------------------------------------------------

	@Override
	public void initialize()
	{

		if (Activator.getInstance().isDebugging())
		{
			System.out.println("openocd.Launch.initialize() " + this);
		}

		super.initialize();

		Runnable initRunnable = new DsfRunnable()
		{
			@Override
			public void run()
			{
				fTracker = new DsfServicesTracker(GdbPlugin.getBundleContext(), fSession.getId());
				// fSession.addServiceEventListener(GdbLaunch.this, null);

				// fInitialized = true;
				// fireChanged();
			}
		};
		
		try
		{
			cleanUpOldLaunchProcesses();
		}
		catch (CoreException e)
		{
			e.printStackTrace();
		}

		// Invoke the execution code and block waiting for the result.
		try
		{
			fExecutor.submit(initRunnable).get();
		}
		catch (InterruptedException e)
		{
			new Status(IStatus.ERROR, Activator.PLUGIN_ID, IDsfStatusConstants.INTERNAL_ERROR,
					"Error initializing launch", e); //$NON-NLS-1$
		}
		catch (ExecutionException e)
		{
			new Status(IStatus.ERROR, Activator.PLUGIN_ID, IDsfStatusConstants.INTERNAL_ERROR,
					"Error initializing launch", e); //$NON-NLS-1$
		}
		
	}

	@Override
	protected void provideDefaults(ILaunchConfigurationWorkingCopy config) throws CoreException
	{

		super.provideDefaults(config);

		if (!config.hasAttribute(IGDBJtagConstants.ATTR_IP_ADDRESS))
		{
			config.setAttribute(IGDBJtagConstants.ATTR_IP_ADDRESS, "localhost"); //$NON-NLS-1$
		}

		if (!config.hasAttribute(IGDBJtagConstants.ATTR_JTAG_DEVICE_ID))
		{
			config.setAttribute(IGDBJtagConstants.ATTR_JTAG_DEVICE_ID, ConfigurationAttributes.JTAG_DEVICE);
		}

		if (!config.hasAttribute(IGDBJtagConstants.ATTR_PORT_NUMBER))
		{
			config.setAttribute(IGDBJtagConstants.ATTR_PORT_NUMBER,
					DefaultPreferences.GDB_SERVER_GDB_PORT_NUMBER_DEFAULT);
		}

		if (!config.hasAttribute(IGDBLaunchConfigurationConstants.ATTR_DEBUG_NAME))
		{
			DefaultPreferences fDefaultPreferences = Activator.getInstance().getDefaultPreferences();
			config.setAttribute(IGDBLaunchConfigurationConstants.ATTR_DEBUG_NAME,
					fDefaultPreferences.getGdbClientExecutable());
		}

		if (Configuration.getDoStartGdbServer(config))
		{
			config.setAttribute(IGDBJtagConstants.ATTR_PORT_NUMBER, DefaultPreferences.GDB_SERVER_GDB_PORT_NUMBER_DEFAULT);
		}
		
		config.setAttribute(DebugPlugin.ATTR_PROCESS_FACTORY_ID, CustomIdfProcessFactory.ID);
	}

	// ------------------------------------------------------------------------

	public void initializeServerConsole(IProgressMonitor monitor) throws CoreException
	{

		if (Activator.getInstance().isDebugging())
		{
			System.out.println("openocd.Launch.initializeServerConsole()");
		}

		boolean doAddServerConsole = Configuration.getDoAddServerConsole(fConfig);

		if (doAddServerConsole)
		{

			// Add the GDB server process to the launch tree
			openOcdServerProcess = addServerProcess(Configuration.getGdbServerCommandName(fConfig));
			LaunchProcessDictionary.getInstance().addProcessToDictionary(getLaunchConfiguration().getName(), SERVER_PROC_KEY, openOcdServerProcess);
			monitor.worked(1);
		}
	}

	public void initializeConsoles(IProgressMonitor monitor) throws CoreException
	{

		if (Activator.getInstance().isDebugging())
		{
			System.out.println("openocd.Launch.initializeConsoles()");
		}

		{
			// Add the GDB client process to the launch tree.
			gdbIProcess = addClientProcess(Configuration.getGdbClientCommandName(fConfig));
			gdbIProcess.setAttribute(IProcess.ATTR_CMDLINE, Configuration.getGdbClientCommandLine(fConfig));
			LaunchProcessDictionary.getInstance().addProcessToDictionary(getLaunchConfiguration().getName(), GDB_PROC_KEY, gdbIProcess);
			monitor.worked(1);
		}
	}

	public IProcess addServerProcess(String label) throws CoreException
	{
		IProcess newProcess = null;
		try
		{
			// Add the server process object to the launch.
			Process serverProc = getDsfExecutor().submit(new Callable<Process>()
			{
				@Override
				public Process call() throws CoreException
				{
					GdbServerBackend backend = fTracker.getService(GdbServerBackend.class);
					if (backend != null)
					{
						return backend.getServerProcess();
					}
					return null;
				}
			}).get();

			// Need to go through DebugPlugin.newProcess so that we can use
			// the overrideable process factory to allow others to override.
			// First set attribute to specify we want to create the gdb process.
			// Bug 210366
			Map<String, String> attributes = new HashMap<String, String>();
			if (serverProc != null)
			{
				newProcess = DebugPlugin.newProcess(this, serverProc, label, attributes);
			}
		}
		catch (InterruptedException e)
		{
			throw new CoreException(new Status(IStatus.ERROR, Activator.PLUGIN_ID, 0,
					"Interrupted while waiting for get process callable.", e)); //$NON-NLS-1$
		}
		catch (ExecutionException e)
		{
			throw (CoreException) e.getCause();
		}
		catch (RejectedExecutionException e)
		{
			throw new CoreException(new Status(IStatus.ERROR, Activator.PLUGIN_ID, 0,
					"Debugger shut down before launch was completed.", e)); //$NON-NLS-1$
		}

		return newProcess;
	}

	// ------------------------------------------------------------------------
	
	@Override
	public void terminate() throws DebugException
	{
		super.terminate();
		
		LaunchProcessDictionary.getInstance().killAllProcessesInLaunch(getLaunchConfiguration().getName());
	}
	
	@Override
	public boolean canDisconnect()
	{
		return true;
	}
	
	@Override
	public boolean canTerminate()
	{
		return true;
	}
	
	
	@Override
	public IProcess[] getProcesses()
	{
		List<IProcess> processes = new ArrayList<>();
	    if (openOcdServerProcess != null) {
	        processes.add(openOcdServerProcess);
	    }
	    if (gdbIProcess != null) {
	        processes.add(gdbIProcess);
	    }
	    return processes.toArray(new IProcess[0]);
	}
	
	private void cleanUpOldLaunchProcesses() throws CoreException
	{
		IProcess serverIProcess = LaunchProcessDictionary.getInstance().getProcessFromDictionary(getLaunchConfiguration().getName(), SERVER_PROC_KEY);
		if (serverIProcess != null && !serverIProcess.isTerminated())
		{
			serverIProcess.terminate();
		}
		
		IProcess gdbIProcess = LaunchProcessDictionary.getInstance().getProcessFromDictionary(getLaunchConfiguration().getName(), GDB_PROC_KEY);
		if(gdbIProcess != null && !gdbIProcess.isTerminated())
		{
			gdbIProcess.terminate();
		}
	}
}
